package com.example.back.pay;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.example.back.bean.PayOrderDetails;
import com.example.back.bean.TopUpConfig;
import com.example.back.bean.UserInfo;
import com.example.back.dao.PayOrderDetailsDao;
import com.example.back.dao.TopUpConfigDao;
import com.example.back.dao.UserDao;
import com.example.back.util.RedisUtils;
import com.mysql.cj.util.LogUtils;
import net.sf.json.JSONObject;
import okhttp3.HttpUrl;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.*;


@RestController
public class WxPayV3Util {

    private static Logger logger =LoggerFactory.getLogger(WxPayV3Util.class);

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private UserDao userDao;

    @Autowired
    private TopUpConfigDao topUpConfigDao;

    @Autowired
    private  PayOrderDetailsDao payOrderDetailsDao;

    @Value("${wxPay.mercId}")
    private  String mercId;

    //证书序列号
    @Value("${wxPay.serial_no}")
    private  String serial_no;

    //证书地址
    @Value("${wxPay.privateKeyFilePath}")
    private  String privateKeyFilePath;

    //APPID
    @Value("${wxPay.appId}")
    private  String appId;

    //APIV3密钥
    @Value("${wxPay.apiV3}")
    private String apiV3;

    public PayOrderDetailsDao getPayOrderDetailsDao() {
        return payOrderDetailsDao;
    }

    public void setPayOrderDetailsDao(PayOrderDetailsDao payOrderDetailsDao) {
        this.payOrderDetailsDao = payOrderDetailsDao;
    }

    public TopUpConfigDao getTopUpConfigDao() {
        return topUpConfigDao;
    }

    public void setTopUpConfigDao(TopUpConfigDao topUpConfigDao) {
        this.topUpConfigDao = topUpConfigDao;
    }

    // http://8.134.114.62:9090/api/wxPay?userId=120005&amounts=6&diamonds=60&sessCode=e07276bcf802d2f40dd2b309f7e07ecb&type=wxPay&sytime=1633868960238&sysign=1db710a26c479a5d79e8054cf636ca9d
    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @RequestMapping("/wxPay")
    public Object wxPay(String userId,String amounts,String diamonds,String sessCode,String type,String sytime,String sysign,String payType) throws Exception {
        logger.info("wxPay----------");
        Map<String,String > codeInfo = new HashMap<>();
        if (StringUtils.isBlank(userId) || StringUtils.isBlank(amounts) || StringUtils.isBlank(diamonds) || StringUtils.isBlank(sessCode) || StringUtils.isBlank(sytime)|| StringUtils.isBlank(sysign)){

            codeInfo.put("code","400");
            codeInfo.put("message","参数数量不符合要求");
            return codeInfo;
        }




        UserInfo userInfo = userDao.getOne(Long.valueOf(userId));
        logger.info("wxUserPay={}",userInfo==null);
        if (userInfo==null ){
            codeInfo.put("code","401");
            codeInfo.put("message","参数异常");
            return codeInfo;
        }


        TopUpConfig topUpConfigs = null;
        //payType  1表示首充礼包，2表示幸运折扣，0表示充钻
        if ("1".equals(payType)){
            Map<Object,Object> firstInfo = userDao.selectFirstCharge();
            logger.info("payParams={},1={}",amounts.equals(firstInfo.getOrDefault("amount",100).toString()),diamonds.equals(firstInfo.getOrDefault("number",88888).toString()));
            if (firstInfo==null || (!amounts.equals(firstInfo.getOrDefault("amount",100).toString()) && !diamonds.equals(firstInfo.getOrDefault("number",88888).toString()))){
                codeInfo.put("code","401");
                codeInfo.put("message","首充参数异常");
                return codeInfo;
            }

        }else if ("2".equals(payType)){
            String msgType = "LuckyPack"+amounts;
            String msgKey = "LuckyPack"+amounts;
            Map<String,Object> map = userDao.luckyDiscountMap(msgType,msgKey);
            if (map==null){
                codeInfo.put("code","401");
                codeInfo.put("message","幸运折扣参数异常");
                return codeInfo;
            }

        }
        else {

            String topUpConfigList = (String) redisUtils.get("allTopUpConfig");
            if(topUpConfigList!=null){
                List<TopUpConfig>  topUpConfigAll = JSON.parseArray(topUpConfigList,TopUpConfig.class);
                for (TopUpConfig topUpConfig : topUpConfigAll) {
                    if (topUpConfig.getDiamonds() == Integer.parseInt(diamonds) && topUpConfig.getMoney() == Integer.parseInt(amounts)) {
                        topUpConfigs = topUpConfig;
                        break;
                    }
                }
            }else {
                topUpConfigs = topUpConfigDao.getTopUpConfig(Integer.parseInt(amounts), Integer.parseInt(diamonds));
            }
            //     logger.info(topUpConfigList+"=====json");

            if (topUpConfigs == null) {
                codeInfo.put("code", "401");
                codeInfo.put("message", "参数异常");
                return codeInfo;
            }
        }

        WxPayRequestBo wxPayRequestBo = new WxPayRequestBo();

        String orderId = UUID.randomUUID().toString().replace("-", "");
        wxPayRequestBo.setAppid("wxee1ab258a62dce8a");
        wxPayRequestBo.setMchid("1614506123");
        wxPayRequestBo.setDescription("充值游戏货币");
        wxPayRequestBo.setOut_trade_no(orderId);
        wxPayRequestBo.setNotify_url("http://119.91.143.179:9090/api/notify");
        wxPayRequestBo.setAttach(payType);
        Amount amount = new Amount();
        Map<Object,Object> firstInfo = null;
        Map<String,Object> map = null;
        if ("1".equals(payType)){
            firstInfo = userDao.selectFirstCharge();
            amount.setTotal(Integer.valueOf( firstInfo.getOrDefault("amount",200).toString()));
        }else if ("2".equals(payType)){
            String msgType = "LuckyPack"+amounts;
            String msgKey = "LuckyPack"+amounts;
            map = userDao.luckyDiscountMap(msgType,msgKey);
            com.alibaba.fastjson.JSONObject json = com.alibaba.fastjson.JSONObject.parseObject(map.get("msgValue").toString()) ;
            amount.setTotal(Integer.valueOf(json.getString("money")));
        }else{

            amount.setTotal(topUpConfigs.getMoney());
        }

        wxPayRequestBo.setAmount(amount);
        String wxPayRequestJsonStr = JSONUtil.toJsonStr(wxPayRequestBo);
        //第一步获取prepay_id
        String prepayId = WxPayV3Util.V3PayGet("v3/pay/transactions/app", wxPayRequestJsonStr, mercId, serial_no, privateKeyFilePath);
        //第二步获取调起支付的参数E:\weiXinSSL\apiclient_key
        JSONObject object = JSONObject.fromObject(WxPayV3Util.WxTuneUp(prepayId, appId, privateKeyFilePath,wxPayRequestBo.getMchid(),wxPayRequestBo.getOut_trade_no()));

        PayOrderDetails payOrderDetails = new PayOrderDetails();

        payOrderDetails.setAppId(appId);
        payOrderDetails.setCreateTime(new Date());
        if ("1".equals(payType)){
            payOrderDetails.setDescription("游戏首充礼包");
            payOrderDetails.setRechargeQuantity(Integer.valueOf( firstInfo.getOrDefault("number",88888).toString()));
            payOrderDetails.setAmount(Integer.valueOf( firstInfo.getOrDefault("amount",200).toString()));

        }else if ("2".equals(payType)){
            com.alibaba.fastjson.JSONObject json = com.alibaba.fastjson.JSONObject.parseObject(map.get("msgValue").toString()) ;
            payOrderDetails.setDescription("游戏幸运折扣");
            payOrderDetails.setRechargeQuantity(Integer.valueOf(json.getString("gold")));

            payOrderDetails.setAmount(Integer.valueOf(json.getString("money")));
        }
        else {
            payOrderDetails.setDescription("游戏充值钻石");
            payOrderDetails.setRechargeQuantity(Integer.valueOf(topUpConfigs.getDiamonds())+Integer.valueOf(topUpConfigs.getExtra()));
            payOrderDetails.setAmount(topUpConfigs.getMoney());
        }

        payOrderDetails.setExt1(payType);
        payOrderDetails.setExt2(null);
        payOrderDetails.setMchId(mercId);
        payOrderDetails.setOrderNumber(orderId);
        payOrderDetails.setPayTime(new Date());
        payOrderDetails.setPayType(0);
        payOrderDetails.setPrepaymentId(prepayId);
        payOrderDetails.setQuantityAfterOperation(0);
        payOrderDetails.setState(0);
        payOrderDetails.setTransactionId(null);
        payOrderDetails.setUserId(userInfo.getUserId());
        payOrderDetails.setUserName(userInfo.getName());

        payOrderDetailsDao.insertPayOrderDetails(payOrderDetails);
        //payOrderDetailsDao.insertPayOrderDetails();

        return object;
    }



    /**
     * 微信支付下单
     *
     * @param url                请求地址（只需传入域名之后的路由地址）
     * @param jsonStr            请求体 json字符串 此参数与微信官方文档一致
     * @param mercId             商户ID
     * @param serial_no          证书序列号
     * @param privateKeyFilePath 私钥的路径
     * @return 订单支付的参数
     * @throws Exception
     */
    public static String V3PayGet(String url, String jsonStr, String mercId, String serial_no, String privateKeyFilePath) throws Exception {
        String body = "";
        //创建httpclient对象
        CloseableHttpClient client = HttpClients.createDefault();
        //创建post方式请求对象
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
        //装填参数
        StringEntity s = new StringEntity(jsonStr, "UTF-8");
        s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
                "application/json"));
        //设置参数到请求对象中
        httpPost.setEntity(s);
        String post = getToken("POST", HttpUrl.parse(url), mercId, serial_no, privateKeyFilePath, jsonStr);
        //设置header信息
        //指定报文头【Content-type】、【User-Agent】
        httpPost.setHeader("Content-type", "application/json");
        httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
        httpPost.setHeader("Accept", "application/json");
        httpPost.setHeader("Authorization",
                "WECHATPAY2-SHA256-RSA2048 " + post);
        //执行请求操作，并拿到结果（同步阻塞）
        CloseableHttpResponse response = client.execute(httpPost);
        //获取结果实体
        HttpEntity entity = response.getEntity();

        if (entity != null) {
            //按指定编码转换结果实体为String类型
            body = EntityUtils.toString(entity, "UTF-8");
            System.out.println("结果："+body);
        }
        EntityUtils.consume(entity);

        //释放链接
        response.close();
        switch (url) {
            case "v3/pay/transactions/app"://返回APP支付所需的参数
                return JSONObject.fromObject(body).getString("prepay_id");
            case "v3/pay/transactions/jsapi"://返回JSAPI支付所需的参数
                return JSONObject.fromObject(body).getString("prepay_id");
            case "v3/pay/transactions/native"://返回native的请求地址
                return JSONObject.fromObject(body).getString("code_url");
            case "v3/pay/transactions/h5"://返回h5支付的链接
                return JSONObject.fromObject(body).getString("h5_url");
        }
        return null;
    }
    /**
     * 微信调起支付参数
     * 返回参数如有不理解 请访问微信官方文档
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_1_4.shtml
     *
     * @param prepayId           微信下单返回的prepay_id
     * @param appId              应用ID(appid)
     * @param privateKeyFilePath 私钥的地址
     * @return 当前调起支付所需的参数
     * @throws Exception
     */
    public static JSONObject WxTuneUp(String prepayId, String appId, String privateKeyFilePath,String mchId,String OutTradeNo) throws Exception {
        String time = System.currentTimeMillis() / 1000 + "";

        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        String packageStr = "prepay_id=" + prepayId;
        ArrayList<String> list = new ArrayList<>();
        list.add(appId);
        list.add(time);
        list.add(OutTradeNo);
        list.add(packageStr);
        //加载签名
        String packageSign = sign(buildSignMessage(list).getBytes(), privateKeyFilePath);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", appId);
        jsonObject.put("partnerid",mchId);
        jsonObject.put("timeStamp", time);
        jsonObject.put("nonceStr", OutTradeNo);
        jsonObject.put("prepayid", prepayId);
        jsonObject.put("signType", "RSA");
        jsonObject.put("package","Sign=WXPay");
        jsonObject.put("paySign", packageSign);
        return jsonObject;
    }


    /**
     * 处理微信异步回调
     *
     * @param request
     * @param response
     */
    @RequestMapping("/notifys")
    public  String notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String privateKey =  apiV3;
        Map<String, String> map = new HashMap<>(12);
        String result = readData(request);
        // 需要通过证书序列号查找对应的证书，verifyNotify 中有验证证书的序列号
        String plainText = verifyNotify(result, privateKey);
        JSONObject json = JSONObject.fromObject(plainText);

        logger.info("回调code:"+json+"     out_trade_no"+json.getString("out_trade_no"));

        if ("SUCCESS".equals(json.getString("trade_state"))){

            PayOrderDetails payOrderDetails = payOrderDetailsDao.selectPayOrderDetails(json.getString("out_trade_no"));

            if (payOrderDetails!=null){
                payOrderDetails.setTransactionId(json.getString("transaction_id"));
                payOrderDetails.setState(1);
                payOrderDetails.setPayTime(new Date());
            }

            int id = payOrderDetailsDao.updatePayOrderDetails(payOrderDetails);

            if (id>0){
                if ("1".equals(json.getString("attach"))){
                    Map<Object,Object> firstInfo = userDao.selectFirstCharge();
                //    userDao.updateUserFreeCardsInfo(payOrderDetails.getUserId(),Long.valueOf(firstInfo.getOrDefault("number",0).toString()));
                    userDao.updateShowUserDiamond(payOrderDetails.getUserId(),Integer.valueOf(firstInfo.getOrDefault("number",10).toString()));
                }else if ("2".equals(json.getString("attach"))){
                    String msgKey = "LuckyPack"+payOrderDetails.getAmount();
                    String msgType = "LuckyPack"+payOrderDetails.getAmount();
                    Map<String,Object> luckyMap = userDao.luckyDiscountMap(msgType,msgKey);
                    logger.info("payOrderUserId={},Quantity={},map={}",payOrderDetails.getUserId(),payOrderDetails.getRechargeQuantity(),luckyMap.size());
                    if (luckyMap!=null && luckyMap.size()>0){
                        userDao.updateLucky(payOrderDetails.getRechargeQuantity(),payOrderDetails.getUserId());
                    }
                }else {
                    userDao.updateUserDiamond(payOrderDetails.getUserId(),payOrderDetails.getRechargeQuantity());
                }
                //订单关闭接口


                logger.info("wxPayUpdateStateOK userId:"+payOrderDetails.getUserId()+" TransactionId:"+json.getString("transaction_id")+" orderNumber:"+payOrderDetails.getOrderNumber()+"  amount:"+payOrderDetails.getAmount());
            }else {
                //      logger.info("wxPayUpdateStateERROR userId={},TransactionId={},orderNumber={},amount={}",payOrderDetails.getUserId(),payOrderDetails.getTransactionId(),payOrderDetails.getOrderNumber(),payOrderDetails.getAmount());
            }
            //订单支付成功后进行订单关闭
/*
            //请求URL
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/sdkphp12345678920201028112429/close");
            //请求body参数
            String reqdata ="{\"mchid\": \""+mchId+"\"}";

            StringEntity entity = new StringEntity(reqdata,"utf-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept", "application/json");

            //完成签名并执行请求
            CloseableHttpResponse response = httpClient.execute(httpPost);
            try {
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode == 200) {
                    System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
                } else if (statusCode == 204) {
                    System.out.println("success");
                } else {
                    System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
                    throw new IOException("request failed");
                }
            } finally {
                response.close();
            }*/

            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "SUCCESS");
        }else {
            logger.info("wxPayERROR message:签名错误");
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "签名错误");
        }

        /*if (StrUtil.isNotEmpty(plainText)) {
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "SUCCESS");
        } else {
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "签名错误");
        }*/
        response.setHeader("Content-type", ContentType.JSON.toString());
        response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
        String out_trade_no = JSONObject.fromObject(plainText).getString("out_trade_no");
        return out_trade_no;
    }

    /**
     * 生成组装请求头
     *
     * @param method             请求方式
     * @param url                请求地址
     * @param mercId             商户ID
     * @param serial_no          证书序列号
     * @param privateKeyFilePath 私钥路径
     * @param body               请求体
     * @return 组装请求的数据
     * @throws Exception
     */
    static String getToken(String method, HttpUrl url, String mercId, String serial_no, String privateKeyFilePath, String body) throws Exception {
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        long timestamp = System.currentTimeMillis() / 1000;
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        String signature = sign(message.getBytes("utf-8"), privateKeyFilePath);
        System.out.println(signature);
        return "mchid=\"" + mercId + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + serial_no + "\","
                + "signature=\"" + signature + "\"";
    }

    /**
     * 生成签名
     *
     * @param message            请求体
     * @param privateKeyFilePath 私钥的路径
     * @return 生成base64位签名信息
     * @throws Exception
     */
    static String sign(byte[] message, String privateKeyFilePath) throws Exception {

        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(privateKeyFilePath));
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    /**
     * 组装签名加载
     *
     * @param method    请求方式
     * @param timestamp 请求时间
     * @param nonceStr  请求随机字符串
     * @param body      请求体
     * @return 组装的字符串
     */
    static String buildMessage(String method, HttpUrl urls, long timestamp, String nonceStr, String body) {
        HttpUrl url =HttpUrl.parse("https://api.mch.weixin.qq.com/v3/pay/transactions/app");

        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    /**
     * 获取私钥。
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    static PrivateKey getPrivateKey(String filename) throws IOException, IOException {
        String content = new String(Files.readAllBytes(Paths.get(filename)), "UTF-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

    /**
     * 构造签名串
     *
     * @param signMessage 待签名的参数
     * @return 构造后带待签名串
     */
    static String buildSignMessage(ArrayList<String> signMessage) {
        if (signMessage == null || signMessage.size() <= 0) {
            return null;
        }
        StringBuilder sbf = new StringBuilder();
        for (String str : signMessage) {
            sbf.append(str).append("\n");
        }
        return sbf.toString();
    }

    /**
     * v3 支付异步通知验证签名
     *
     * @param body 异步通知密文
     * @param key  api 密钥
     * @return 异步通知明文
     * @throws Exception 异常信息
     */
    static String verifyNotify(String body, String key) throws Exception {
        // 获取平台证书序列号
        cn.hutool.json.JSONObject resultObject = JSONUtil.parseObj(body);
        cn.hutool.json.JSONObject resource = resultObject.getJSONObject("resource");
        //     logger.info("支付是否成功："+resource.getJSONObject("event_type"));
        String cipherText = resource.getStr("ciphertext");
        String nonceStr = resource.getStr("nonce");
        String associatedData = resource.getStr("associated_data");




        logger.info("回调参数cipherText="+cipherText+"  nonceStr:"+nonceStr+"  associatedData:"+associatedData+"  key:"+key);
        AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));
        logger.info("开始解密");
        // 密文解密
        return aesUtil.decryptToString(
                associatedData.getBytes(StandardCharsets.UTF_8),
                nonceStr.getBytes(StandardCharsets.UTF_8),
                cipherText
        );
    }

    /**
     * 处理返回对象
     *
     * @param request
     * @return
     */
    static String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
